﻿using Ndns.ServerHost.Abstractions;
using System;
using Ndns.ServerHost.Abstractions.Services;
using System.Threading.Tasks;
using System.Net;
using Ndns.Net;
using Ndns.Packet.Parser;
using Ndns.Packet.Abstractions;
using System.Threading;

namespace Ndns.Listener
{
    class DnsListenerMiddleware : IMiddleware
    {
        internal readonly DnsProtocol _protocol;
        internal readonly IPEndPoint _ipEndPoint;

        private DnsPacketParser _parser;

        private IServiceCollection _services;
        private RequestDelegate _next;
        private IContextFactoryService _contextFactory;
        internal ILogger _logger;

        private IDnsServer _server;

        public DnsListenerMiddleware(DnsProtocol protocol, IPEndPoint ipEndPoint)
        {
            _protocol = protocol;
            _ipEndPoint = ipEndPoint;
            _parser = new DnsPacketParser(SharedObjects.ArrayPool);
        }
        public void RegisterServices(IServiceCollection services, RequestDelegate next)
        {
            _services = services;
            _next = next;
            _contextFactory = services.Get<IContextFactoryService>();
            _logger = services.Get<ILoggerProvider>().GetLogger<DnsListenerMiddleware>();
        }

        public void Start()
        {
            if (_protocol == DnsProtocol.Tcp)
            {
                _server = new DnsTcpServer(_ipEndPoint, _parser);
            }
            else
            {
                _server = new DnsUdpServer(_ipEndPoint, _parser);
            }
            _server.Tag = this;
            _server.SetHandler(new HandlerForDnsListenerMiddleware());
            _server.Start();
            if (_server.State != DnsServerState.Running)
            {
                _logger.LogFatal(string.Empty, $"Failed to start listener on [{_protocol}] {_ipEndPoint}");
            }
        }

        private static string GenerateTraceIdentifier() => Guid.NewGuid().ToString("N").Substring(0, 12).ToUpperInvariant();

        internal async Task<DnsPacket> OnReceivedAsync(DnsPacket packet, IPEndPoint source, CancellationToken cancellationToken)
        {
            var traceIdentifier = GenerateTraceIdentifier();
            _logger.LogInfo(traceIdentifier, $"Received request from [{_protocol}] {source.Address}:{source.Port}");

            var conn = _contextFactory.CreateConnectionInfo(_protocol, source.Address, source.Port, _ipEndPoint.Address, _ipEndPoint.Port);
            var context = _contextFactory.CreateContext(traceIdentifier, conn, null, false, cancellationToken);
            
            var request = context.Request;
            var response = context.Response;

            request.AcceptNonAuthenticatedData = packet.AcceptNonAuthenticatedData;
            request.RecursionDesired = packet.RecursionDesired;
            request.OperationCode = packet.OpCode;
            foreach (var query in packet.Queries)
            {
                request.Queries.Add(query.Clone());
            }

            try
            {
                await _next(context);

                foreach (var item in response.AnswerRRs)
                {
                    packet.AnswerRRs.Add(item);
                }
                foreach (var item in response.AuthoritativeNSs)
                {
                    packet.AuthoritativeNSs.Add(item);
                }
                foreach (var item in response.AdditionalRRs)
                {
                    packet.AdditionalRRs.Add(item);
                }
            }
            catch (OperationCanceledException)
            {
                _logger.LogWarning(traceIdentifier, $"Cancellation triggerred. Ignoring request.");
                response.IgnoreRequest = true;
            }
            catch (Exception ex)
            {
                _logger.LogError(traceIdentifier, $"Unhandled exception caught.", ex);
                packet.IsResponsePacket = true;
                packet.ReplyCode = DnsReplyCode.ServerFailure;
            }

            if (response.IgnoreRequest)
            {
                _logger.LogWarning(traceIdentifier, $"Request is ignored.");
                return null;
            }
            
            packet.IsResponsePacket = true;
            packet.ServerAuthoritative = response.ServerAuthoritative;
            packet.AnswerAuthenticated = response.AnswerAuthenticated;
            packet.RecursionAvailable = response.RecursionAvailable;
            packet.ReplyCode = response.ReplyCode;

            _logger.LogInfo(traceIdentifier, $"Sending pesponse to [{_protocol}] {source.Address}:{source.Port}");

            return packet;
        }

        public Task InvokeAsync(DnsContext context)
        {
            return _next(context);
        }

        #region IDisposable Support
        private bool disposedValue = false; // 要检测冗余调用

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // 释放托管状态(托管对象)。
                    (_server as IDisposable)?.Dispose();
                }

                // 释放未托管的资源(未托管的对象)并在以下内容中替代终结器。
                // 将大型字段设置为 null。
                _server = null;

                disposedValue = true;
            }
        }

        // 仅当以上 Dispose(bool disposing) 拥有用于释放未托管资源的代码时才替代终结器。
        // ~DnsListenerMiddleware() {
        //   // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
        //   Dispose(false);
        // }

        // 添加此代码以正确实现可处置模式。
        public void Dispose()
        {
            // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
            Dispose(true);
            // 如果在以上内容中替代了终结器，则取消注释以下行。
            // GC.SuppressFinalize(this);
        }
        #endregion
    }

    class HandlerForDnsListenerMiddleware : IDnsRequestHandler
    {
        public Task<DnsPacket> OnReceivedAsync(IDnsServer server, DnsPacket packet, EndPoint source, CancellationToken cancellationToken)
        {
            var middleware = (DnsListenerMiddleware)server.Tag;
            return middleware.OnReceivedAsync(packet, (IPEndPoint)source, cancellationToken);
        }

        public void OnFatalError(IDnsServer server, Exception ex)
        {
            var middleware = (DnsListenerMiddleware)server.Tag;
            middleware._logger.LogFatal(string.Empty, $"Fatal error occurred.", ex);
        }

        public void OnStarted(IDnsServer server)
        {
            var middleware = (DnsListenerMiddleware)server.Tag;
            middleware._logger.LogInfo(string.Empty, $"Started listener on [{middleware._protocol}] {middleware._ipEndPoint}");
        }

        public void OnStopped(IDnsServer server)
        {
            var middleware = (DnsListenerMiddleware)server.Tag;
            middleware._logger.LogInfo(string.Empty, $"Stopped listener on [{middleware._protocol}] {middleware._ipEndPoint}");
        }

        public void OnStarting(IDnsServer server)
        {
            var middleware = (DnsListenerMiddleware)server.Tag;
            middleware._logger.LogTrace(string.Empty, $"Starting listener on [{middleware._protocol}] {middleware._ipEndPoint}");
        }

        public void OnStopping(IDnsServer server)
        {
            var middleware = (DnsListenerMiddleware)server.Tag;
            middleware._logger.LogTrace(string.Empty, $"Stopping listener on [{middleware._protocol}] {middleware._ipEndPoint}");
        }
    }
}
